﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Diagnostics;
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
using System.IO;


namespace NetAcademia.Web.Mvc
{
    public class HtmlLaundryModelBinder:DefaultModelBinder
    {
        
        string defaultWhitelistName;
        
        HtmlLaundry defaultLaundry;
        IDictionary<string, HtmlLaundry> Laundries;

        public bool TryToResolveStringParameterAttributes { get; set; }

        public HtmlLaundryModelBinder(string defaultWhitelistName)
        {
            Laundries = new Dictionary<string, HtmlLaundry>(StringComparer.OrdinalIgnoreCase);
            this.defaultWhitelistName = defaultWhitelistName;
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var bm = base.BindModel(controllerContext, bindingContext);

            if (TryToResolveStringParameterAttributes && bm != null && bindingContext.ModelType == typeof(string) && bindingContext.ModelName.ToLower() != "id")
            {
                //try to find parameter attribute, it's confusing I did not get it in the binding context! This way I can not be sure to resolve correctly because of missin information - just try.
                var controllertype = controllerContext.Controller.GetType();
                var methods = controllertype.GetMethods(System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly);

                string actionName = ((string)controllerContext.RequestContext.RouteData.Values["action"]).ToLower();
                string parameterName = bindingContext.ModelName.ToLower();

                var vv = (from method in methods
                          where method.Name.ToLower() == actionName
                          && method.GetParameters().Any(pi => pi.Name.ToLower() == parameterName && pi.ParameterType == typeof(string))
                          select method).FirstOrDefault();

                if (vv != null)
                {
                    var info = vv.GetParameters().First(pi=>pi.Name.ToLower() == parameterName);
                    var wl = info.GetCustomAttributes(typeof(WhitelistAttribute), false).FirstOrDefault() as WhitelistAttribute;
                    if (wl != null)
                    {
                        EnsureLaundry(controllerContext, wl);
                        bm = Laundries[wl.WhitelistName].CleanHtml(bm.ToString());
                    }
                }
            }

            return bm;
        }

    
        protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            if (this.defaultLaundry == null)
            {
                this.defaultLaundry = GetLaundry(controllerContext, defaultWhitelistName);
                if (this.defaultLaundry == null)
                    throw new FileNotFoundException(string.Format("Can't find the whitelist file '{0}' for the default Html Laundry. You shoud place your *.whitelist files under /content or /content/htmllaundry or /app_data or app_data/htmllaundry folder.", defaultWhitelistName), defaultWhitelistName);
            }

            var value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);

            if (propertyDescriptor != null)
            {
                var whiteListAttribute = propertyDescriptor.Attributes.OfType<WhitelistAttribute>().FirstOrDefault();
                if (whiteListAttribute != null)
                {
                    EnsureLaundry(controllerContext, whiteListAttribute);
                    value = Laundries[whiteListAttribute.WhitelistName].CleanHtml(value.ToString());
                }
                else
                {
                    var allowHtml = propertyDescriptor.Attributes.OfType<AllowHtmlAttribute>().Any();
                    var htmlHint = propertyDescriptor.Attributes.OfType<UIHintAttribute>().Any(h=>h.UIHint.StartsWith("html", StringComparison.InvariantCultureIgnoreCase));
                    var htmlDataType = propertyDescriptor.Attributes.OfType<DataTypeAttribute>().Any(d=>d.DataType == DataType.Html);

                    if (value != null && (allowHtml || htmlHint || htmlDataType))
                    {
                        value = defaultLaundry.CleanHtml(value.ToString());
                    }
                }

                Debug.WriteLine(string.Format("get property value: {0} = {1}", propertyDescriptor.Name, value));
            }

            return  value;
        }

        private void EnsureLaundry(ControllerContext controllerContext, WhitelistAttribute wl)
        {
            if (!Laundries.ContainsKey(wl.WhitelistName))
            {
                var laundry = GetLaundry(controllerContext, wl.WhitelistName);
                if (laundry == null)
                {
                    if (!wl.CanUseDefaultWhitelistIfMissing)
                        throw new FileNotFoundException(string.Format("Can't find the whitelist file '{0}'. You shoud place your *.whitelist files under /content or /content/htmllaundry or /app_data or app_data/htmllaundry folder.", wl.WhitelistName), wl.WhitelistName);

                    laundry = defaultLaundry;
                }
                Laundries[wl.WhitelistName] = laundry;
            }
        }

        /// <summary>
        /// Loads a whitelist into an XDocument.
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="whitelistName">It must be the whitelist's filename only with or without the .whitelist extension</param>
        /// <returns>Loaded XDocument or null if the given file does not exist.</returns>
        /// <remarks>
        /// <list type="number">
        /// <listheader>
        /// <description>Searched folders</description>
        /// </listheader>
        /// <attribute>content/htmllaundry/</attribute>
        /// <attribute>content/</attribute>
        /// <attribute>app_data/htmllaundry/</attribute>
        /// <attribute>app_data/</attribute>
        /// </list>
        /// </remarks>
        public static HtmlLaundry GetLaundry(ControllerContext controllerContext, string whitelistName)
        {
            var server = controllerContext.RequestContext.HttpContext.Server;

            XDocument whitelist = null;
            //files: name.whitelist
            if (!whitelistName.EndsWith(".whitelist", StringComparison.OrdinalIgnoreCase))
                whitelistName += ".whitelist";

            string filename;

            filename = server.MapPath("~/content/htmllaundry/" + whitelistName);
            if (File.Exists(filename))
            {
                whitelist = XDocument.Load(filename);
            }
            else if (File.Exists(filename = server.MapPath("~/content/" + whitelistName)))
            {
                    whitelist = XDocument.Load(filename);
            }
            else if (File.Exists(filename = server.MapPath("~/app_data/htmllaundry/" + whitelistName)))
            {
                whitelist = XDocument.Load(filename);
            }
            else if (File.Exists(filename = server.MapPath("~/app_data/" + whitelistName)))
            {
                whitelist = XDocument.Load(filename);
            }

            if (whitelist != null)
               
                return new HtmlLaundry(whitelist);
            else
                return null;
        }
    }
}